package instruction

import (
	"gitee.com/microwww/jvm-go/jvm/class"
	"gitee.com/microwww/jvm-go/jvm/rtda"
	"gitee.com/microwww/jvm-go/jvm/tool"
)

const (
	AT_BOOLEAN = 4  // [Z
	AT_CHAR    = 5  // [C
	AT_FLOAT   = 6  // [F
	AT_DOUBLE  = 7  // [D
	AT_BYTE    = 8  // [B
	AT_SHORT   = 9  // [S
	AT_INT     = 10 // [I
	AT_LONG    = 11 // [J
)

var arry_class_type = []string{
	"", "", "", "",
	"[Z", "[C", "[F", "[D",
	"[B", "[S", "[I", "[J",
}

type NewArray struct {
	_8_fetch
}

func (f *NewArray) Execute(fr *rtda.MethodFrame) {
	loader := fr.Method().Class().ClassLoader()
	clz := loader.DefineClass(arry_class_type[f.Index])
	count := fr.OperationStack().PopInt()
	if count < 0 {
		panic("java.lang.NegativeArraySizeException")
	}
	var arr = class.NewArray(clz, count)
	fr.OperationStack().PushRef(arr)
}

type ANewArray struct {
	_16_fetch
}

func (f *ANewArray) Execute(fr *rtda.MethodFrame) {
	clazz := fr.Method().Class()
	loader := clazz.ClassLoader()
	cr := clazz.ConstantPool().GetConstant(f.Index).(*class.ClassRef)
	cr.ResolveClass()
	clz := loader.DefineClass("[" + cr.Class().Name())
	count := fr.OperationStack().PopInt()
	if count < 0 {
		panic("java.lang.NegativeArraySizeException")
	}
	var arr = class.NewArray(clz, count)
	fr.OperationStack().PushRef(arr)
}

func toArrayName(name string) string {
	if name[0] == '[' {
		return name
	}
	return "[" + name + ";"
}

type ArrayLength struct {
	Noop
}

func (f *ArrayLength) Execute(fr *rtda.MethodFrame) {
	ref := fr.OperationStack().PopRef()
	if ref == nil {
		panic("java.lang.NullPointerException")
	}
	l := ref.ArrayLength()
	fr.OperationStack().PushInt(l)
}

type AALoad struct {
	Noop
}

func (f *AALoad) Execute(fr *rtda.MethodFrame) {
	stack := fr.OperationStack()
	index := stack.PopInt()
	refs := stack.PopRef().Refs()
	if refs == nil {
		panic("java.lang.NullPointerException")
	}
	if index < 0 || int32(len(refs)) <= index {
		panic("java.lang.ArrayIndexOutOfBoundsException")
	}
	stack.PushRef(refs[index])
}

type BALoad struct {
	Noop
}

func (f *BALoad) Execute(fr *rtda.MethodFrame) {
	stack := fr.OperationStack()
	index := stack.PopInt()
	refs := stack.PopRef().Bytes()
	if refs == nil {
		panic("java.lang.NullPointerException")
	}
	if index < 0 || int32(len(refs)) <= index {
		panic("java.lang.ArrayIndexOutOfBoundsException")
	}
	stack.PushInt(int32(refs[index]))
}

type CALoad struct {
	Noop
}

func (f *CALoad) Execute(fr *rtda.MethodFrame) {
	stack := fr.OperationStack()
	index := stack.PopInt()
	refs := stack.PopRef().Chars()
	if refs == nil {
		panic("java.lang.NullPointerException")
	}
	if index < 0 || int32(len(refs)) <= index {
		panic("java.lang.ArrayIndexOutOfBoundsException")
	}
	stack.PushInt(int32(refs[index]))
}

type DALoad struct {
	Noop
}

func (f *DALoad) Execute(fr *rtda.MethodFrame) {
	stack := fr.OperationStack()
	index := stack.PopInt()
	refs := stack.PopRef().Doubles()
	if refs == nil {
		panic("java.lang.NullPointerException")
	}
	if index < 0 || int32(len(refs)) <= index {
		panic("java.lang.ArrayIndexOutOfBoundsException")
	}
	stack.PushDouble(refs[index])
}

type FALoad struct {
	Noop
}

func (f *FALoad) Execute(fr *rtda.MethodFrame) {
	stack := fr.OperationStack()
	index := stack.PopInt()
	refs := stack.PopRef().Floats()
	if refs == nil {
		panic("java.lang.NullPointerException")
	}
	if index < 0 || int32(len(refs)) <= index {
		panic("java.lang.ArrayIndexOutOfBoundsException")
	}
	stack.PushFloat(refs[index])
}

type IALoad struct {
	Noop
}

func (f *IALoad) Execute(fr *rtda.MethodFrame) {
	stack := fr.OperationStack()
	index := stack.PopInt()
	refs := stack.PopRef().Ints()
	if refs == nil {
		panic("java.lang.NullPointerException")
	}
	if index < 0 || int32(len(refs)) <= index {
		panic("java.lang.ArrayIndexOutOfBoundsException")
	}
	stack.PushInt(refs[index])
}

type LALoad struct {
	Noop
}

func (f *LALoad) Execute(fr *rtda.MethodFrame) {
	stack := fr.OperationStack()
	index := stack.PopInt()
	refs := stack.PopRef().Longs()
	if refs == nil {
		panic("java.lang.NullPointerException")
	}
	if index < 0 || int32(len(refs)) <= index {
		panic("java.lang.ArrayIndexOutOfBoundsException")
	}
	stack.PushLong(refs[index])
}

type SALoad struct {
	Noop
}

func (f *SALoad) Execute(fr *rtda.MethodFrame) {
	stack := fr.OperationStack()
	index := stack.PopInt()
	refs := stack.PopRef().Shorts()
	if refs == nil {
		panic("java.lang.NullPointerException")
	}
	if index < 0 || int32(len(refs)) <= index {
		panic("java.lang.ArrayIndexOutOfBoundsException")
	}
	stack.PushInt(int32(refs[index]))
}

type AAStore struct {
	Noop
}

func (f *AAStore) Execute(fr *rtda.MethodFrame) {
	stack := fr.OperationStack()
	val := stack.PopRef()
	index := stack.PopInt()
	refs := stack.PopRef().Refs()
	if refs == nil {
		panic("java.lang.NullPointerException")
	}
	if index < 0 || int32(len(refs)) <= index {
		panic("java.lang.ArrayIndexOutOfBoundsException")
	}
	refs[index] = val
}

type BAStore struct {
	Noop
}

func (f *BAStore) Execute(fr *rtda.MethodFrame) {
	stack := fr.OperationStack()
	val := stack.PopInt()
	index := stack.PopInt()
	refs := stack.PopRef().Bytes()
	if refs == nil {
		panic("java.lang.NullPointerException")
	}
	if index < 0 || int32(len(refs)) <= index {
		panic("java.lang.ArrayIndexOutOfBoundsException")
	}
	refs[index] = int8(val)
}

type CAStore struct {
	Noop
}

func (f *CAStore) Execute(fr *rtda.MethodFrame) {
	stack := fr.OperationStack()
	val := stack.PopInt()
	index := stack.PopInt()
	refs := stack.PopRef().Chars()
	if refs == nil {
		panic("java.lang.NullPointerException")
	}
	if index < 0 || int32(len(refs)) <= index {
		panic("java.lang.ArrayIndexOutOfBoundsException")
	}
	refs[index] = uint16(val)
}

type DAStore struct {
	Noop
}

func (f *DAStore) Execute(fr *rtda.MethodFrame) {
	stack := fr.OperationStack()
	val := stack.PopDouble()
	index := stack.PopInt()
	refs := stack.PopRef().Doubles()
	if refs == nil {
		panic("java.lang.NullPointerException")
	}
	if index < 0 || int32(len(refs)) <= index {
		panic("java.lang.ArrayIndexOutOfBoundsException")
	}
	refs[index] = val
}

type FAStore struct {
	Noop
}

func (f *FAStore) Execute(fr *rtda.MethodFrame) {
	stack := fr.OperationStack()
	val := stack.PopFloat()
	index := stack.PopInt()
	refs := stack.PopRef().Floats()
	if refs == nil {
		panic("java.lang.NullPointerException")
	}
	if index < 0 || int32(len(refs)) <= index {
		panic("java.lang.ArrayIndexOutOfBoundsException")
	}
	refs[index] = val
}

type IAStore struct {
	Noop
}

func (f *IAStore) Execute(fr *rtda.MethodFrame) {
	stack := fr.OperationStack()
	val := stack.PopInt()
	index := stack.PopInt()
	refs := stack.PopRef().Ints()
	if refs == nil {
		panic("java.lang.NullPointerException")
	}
	if index < 0 || int32(len(refs)) <= index {
		panic("java.lang.ArrayIndexOutOfBoundsException")
	}
	refs[index] = val
}

type LAStore struct {
	Noop
}

func (f *LAStore) Execute(fr *rtda.MethodFrame) {
	stack := fr.OperationStack()
	val := stack.PopLong()
	index := stack.PopInt()
	refs := stack.PopRef().Longs()
	if refs == nil {
		panic("java.lang.NullPointerException")
	}
	if index < 0 || int32(len(refs)) <= index {
		panic("java.lang.ArrayIndexOutOfBoundsException")
	}
	refs[index] = val
}

type SAStore struct {
	Noop
}

func (f *SAStore) Execute(fr *rtda.MethodFrame) {
	stack := fr.OperationStack()
	val := stack.PopInt()
	index := stack.PopInt()
	refs := stack.PopRef().Shorts()
	if refs == nil {
		panic("java.lang.NullPointerException")
	}
	if index < 0 || int32(len(refs)) <= index {
		panic("java.lang.ArrayIndexOutOfBoundsException")
	}
	refs[index] = int16(val)
}

// MultiANewArray 格式如: int[][][] ints = new int[1][2][3], 而 int[][][] ints = new int[1][][]  则是先生成一维数组A(ANewArray 指令), 然后生成多个一维数组 分别赋值给A数组的每一个元素
type MultiANewArray struct {
	index      uint16
	dimensions uint8
}

func (f *MultiANewArray) FetchOperands(buf *tool.Buffer) {
	f.index = buf.ReadUint16()
	f.dimensions, _ = buf.ReadByte()
}

func (f *MultiANewArray) Execute(fr *rtda.MethodFrame) {
	cr := fr.Method().Class().ConstantPool().GetConstant(uint(f.index)).(*class.ClassRef)
	cr.ResolveClass()
	stack := fr.OperationStack()

	dims := make([]int32, f.dimensions)
	for i := int(f.dimensions) - 1; i >= 0; i-- {
		dims[i] = stack.PopInt()
		if dims[i] < 0 {
			panic("java.lang.NegativeArraySizeException")
		}
	}

	array := createArray(dims, cr.Class())
	stack.PushRef(array)
}

func createArray(dims []int32, clz *class.Class) *class.Object {
	array := class.NewArray(clz, dims[0])
	if len(dims) > 1 {
		objs := array.Refs()
		if clz.Name()[0] != '[' {
			panic("MultiANewArray must [[+")
		}
		chi := clz.ClassLoader().DefineClass(clz.Name()[1:])
		for i := dims[0] - 1; i >= 0; i-- {
			objs[i] = createArray(dims[1:], chi)
		}
	}
	return array
}
